package snmp

import (
	"github.com/gosnmp/gosnmp"
	"github.com/kataras/golog"
	model "sdn-executor/model/snmp"
	"sdn-executor/utils/erros"
	"strings"
	"time"
)

type SNMPClient struct {
	gosnmp *gosnmp.GoSNMP
}

func NewSNMPClient(query *model.BaseQuery) (*SNMPClient, *erros.Error) {
	var port uint16 = 161
	if query.Port > 0 {
		port = query.Port
	}

	g := &gosnmp.GoSNMP{
		Target:             query.Device,
		Port:               port,
		Transport:          "udp",
		Community:          query.Community,
		Version:            query.Version,
		Timeout:            query.TimeoutMs * time.Millisecond,
		Retries:            query.Retries,
		ExponentialTimeout: true,
		MaxOids:            gosnmp.MaxOids,
	}

	err := g.Connect()
	if err != nil {
		golog.Errorf("connect snmp server failed. device: %s, err: %+v", query.Device, err)
		return nil, erros.New(erros.CodeAbnormalDependencyService, "connect snmp server failed. err: %v", err)
	}

	return &SNMPClient{gosnmp: g}, nil
}

func (c *SNMPClient) Get(oids []string) ([]*model.PDU, *erros.Error) {
	result, err := c.gosnmp.Get(oids)
	if err != nil {
		golog.Errorf("snmp get failed. oids: %s", strings.Join(oids, ","))
		return nil, erros.New(erros.CodeAbnormalDependencyService, "snmp get failed. err: %v", err)
	}

	return parseSnmpPDUs(result.Variables), nil
}

func (c *SNMPClient) GetNext(oids []string) ([]*model.PDU, *erros.Error) {
	result, err := c.gosnmp.GetNext(oids)
	if err != nil {
		golog.Errorf("snmp get next failed. oids: %s", strings.Join(oids, ","))
		return nil, erros.New(erros.CodeAbnormalDependencyService, "snmp get next failed. err: %v", err)
	}

	return parseSnmpPDUs(result.Variables), nil
}

func (c *SNMPClient) GetBulk(oids []string, nonRepeaters uint8, maxRepetitions uint32) ([]*model.PDU, *erros.Error) {
	result, err := c.gosnmp.GetBulk(oids, nonRepeaters, maxRepetitions)
	if err != nil {
		golog.Errorf("snmp get bulk failed. oids: %s", strings.Join(oids, ","))
		return nil, erros.New(erros.CodeAbnormalDependencyService, "snmp get bulk failed. err: %v", err)
	}

	return parseSnmpPDUs(result.Variables), nil
}

func (c *SNMPClient) BulkWalk(oid string) ([]*model.PDU, *erros.Error) {
	results, err := c.gosnmp.BulkWalkAll(oid)
	if err != nil {
		golog.Errorf("snmp bulk walk failed. oid: %s", oid)
		return nil, erros.New(erros.CodeAbnormalDependencyService, "snmp bulk walk failed. err: %v", err)
	}

	return parseSnmpPDUs(results), nil
}

func (c *SNMPClient) Walk(oid string) ([]*model.PDU, *erros.Error) {
	results, err := c.gosnmp.WalkAll(oid)
	if err != nil {
		golog.Errorf("snmp walk failed. oid: %s", oid)
		return nil, erros.New(erros.CodeAbnormalDependencyService, "snmp walk failed. err: %v", err)
	}

	return parseSnmpPDUs(results), nil
}

func (c *SNMPClient) Close() {
	if c != nil && c.gosnmp != nil && c.gosnmp.Conn != nil {
		if err := c.gosnmp.Conn.Close(); err != nil {
			golog.Errorf("close gosnmp conn failed. device: %s", c.gosnmp.Target)
		}
	}
}

func parseSnmpPDU(pdu gosnmp.SnmpPDU) *model.PDU {

	result := model.PDU{}
	result.OID = pdu.Name
	result.Value = pdu.Value
	switch pdu.Type {
	case gosnmp.Integer:
		result.Type = "integer"
	case gosnmp.Counter64:
		result.Type = "long"
	case gosnmp.Counter32:
		result.Type = "integer"
	case gosnmp.Uinteger32:
		result.Type = "unsigned integer"
	case gosnmp.Boolean:
		result.Type = "bool"
	case gosnmp.Null:
		result.Type = "null"
	default:
		result.Type = "string"
	}

	return &result
}

func parseSnmpPDUs(results []gosnmp.SnmpPDU) []*model.PDU {
	pdus := []*model.PDU{}
	for _, item := range results {
		pdu := parseSnmpPDU(item)
		pdus = append(pdus, pdu)
	}

	return pdus
}
